package com.it.gmall.mq.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.it.gmall.mq.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import springfox.documentation.spring.web.json.Json;

import javax.annotation.PostConstruct;

@Configuration
@Slf4j
public class MqProducerAckConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    /*配置类实现了RabbitTemplate的接口 但是并没有注入
    * 所以需要设置
    *
    * 方法：没有人调用 相当于没有注入成功
    * 构造器：构造执行的时候 RabbitTemplate应该还没有被注入
    * 后置注解：1.前置 2.实例化(实例化的时候注入了RabbitTemplate) 3.后置
    *
    * 所以此处采用后置注解的方法*/
    @PostConstruct
    public void MqProducerAckConfig() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /*交换机应答 无论成功和失败都会应答*/
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){ // 应答成功
            log.info("交换机应答成功:"+ JSON.toJSONString(correlationData));
        }else{ // 应答失败
            log.error("交换机应答失败:"+cause);

            // 如果交换机应答失败 重新发送 发送三次
            reSendMsg(correlationData);
        }
    }

    // 重新发送消息封装
    /*重发消息需要的参数：
    * 交换机
    * routing-key
    * 消息
    * 重发次数*/
    private void reSendMsg(CorrelationData correlationData) {
        // GmallCorrelationData扩充了CorrelationData  继承自CorrelationData
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData)correlationData;

        // 判断重发次数(可以重发3次)
        int count = gmallCorrelationData.getRetryCount();
        if(count<3){ // 还可以重发

            count++; // 累加消息的重发次数
            gmallCorrelationData.setRetryCount(count); // 设置消息的重发次数到对象中

            // 更改缓存中存储的gmallCorrelationData对象的重发次数
            redisTemplate.opsForValue().set(gmallCorrelationData.getId(),JSONObject.toJSONString(gmallCorrelationData));

            // 重发消息
            // 交换机 routingKey msg CorrelationData对象
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),gmallCorrelationData);
            log.info("交换机重新发送消息,发送的次数是:{}",count);

        }else{
            log.error("消息重发超过三次,消息是:{}",JSON.toJSONString(gmallCorrelationData));
        }
    }

    /*队列应答 只有失败才会应答*/
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("队列应答失败");
        log.error("replyCode响应码:"+replyCode);
        log.error("replyText响应文本:"+replyText);
        log.error("exchange交换机:"+exchange);
        log.error("routingKey:"+routingKey);

        /*当队列应答失败之后 需要重新发送消息
        * 重新发送3次
        *
        * 重发消息：
        * 交换机 routingkey 消息 重发次数
        *
        * 重发次数如何获取？
        * 1.借用外力 使用redis
        * 2.使用Message对象本身*/

        // 从message对象中取id
        String uuid = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
        // 从缓存中取字符串
        String  gmallCorrelationDataStr = (String) redisTemplate.opsForValue().get(uuid);
        // 将字符串转换对象 因为在存储的时候已经转换为字符串 所以要先取出来字符串 在转换
        GmallCorrelationData correlationData = JSONObject.parseObject(gmallCorrelationDataStr,GmallCorrelationData.class);
        if(!correlationData.isDelayMsg()){ // 不是插件的延迟消息
            // 重发消息
            reSendMsg(correlationData);
        }else { // 是插件的延迟消息
            // 此处需要插件发送延迟消息的bug 如果不是卡在交换机 而是真正的队列应答失败 那应该怎么做
            log.info("是插件的延迟消息，可以选择不重发");
        }

    }
}
